package wykop.download.process;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.FileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import wykop.analyze.CreateArffFile;
import wykop.dataBase.UserManager;
import wykop.download.PageDownloader;
import wykop.main.configuration.Configuration;
import wykop.main.configuration.ConfigurationSection;
import wykop.page.WykopId;
import wykop.page.WykopPage;
import wykop.parser.HibernateUtil;

public class UpdateDatabase implements Runnable {

	private final PopulateDatabaseConf configuration;
	private final SessionFactory factory;
	
	public UpdateDatabase( PopulateDatabaseConf configuration, SessionFactory factory )
	{
		this.configuration = configuration;
		this.factory = factory;
	}
	
	private void createAndStorePage(final ExecutorService executor,
			final Queue<Integer> idsOfRemainingPages,
			final PageDownloader pageDownloader,
			final Queue<PageRequest> requests) {
		
		final int newId = idsOfRemainingPages.poll();
		System.out.println("Issuing request for " + newId);
		final Future<?> future = executor.submit(createAndStorePage(newId,pageDownloader));
		requests.add(new PageRequest(newId, future, configuration.pageRetries));
	}

	/**
	 * Returns a runnable, which stores a page, with a given number, in a
	 * database.
	 * 
	 * @param number
	 * @param downloader
	 * @return
	 */
	private Runnable createAndStorePage(final int number,
			final PageDownloader downloader) {
		return new Runnable() {
			@Override
			public void run() {

				final Session session = factory.getCurrentSession();
				session.beginTransaction();

				final WykopId wykopId = new WykopId();
				wykopId.setWykopId(number);
				try {
					final WykopPage page = downloader.download(number);
					if (page == null) {
						wykopId.setErrorMessage("404");
					} else {
						wykopId.setPage(page);
						session.saveOrUpdate(page);
					}

				} catch (final Exception e) {
					wykopId.setErrorMessage(e.getLocalizedMessage());
				}
				updateId(wykopId, session);

				session.getTransaction().commit();

			}
		};
	}

	/**
	 * Returns ids of all pages, which we wish do download
	 * 
	 * @param lastPage
	 * @return
	 */
	private static BlockingQueue<Integer> idsOfAllPages(final int lastPage) {
		final List<Integer> list = new ArrayList<Integer>();

		final Session session = HibernateUtil.getSessionFactory()
				.getCurrentSession();
		session.beginTransaction();

		for (int i = 1; i <= lastPage; i++) {
			final WykopId id = (WykopId) session.get(WykopId.class, i);
			if (id == null || id.getPage() == null) {
				list.add(i);
			}
		}

		Collections.shuffle(list);

		session.getTransaction().commit();

		return new LinkedBlockingQueue<Integer>(list);
	}

	public void run()
	{
		try
		{
			populateDatabase();
		}
		catch(Exception e)
		{
			throw new RuntimeException( e );
		}
	}

	/**
	 * Adds all pages to the database
	 * 
	 * @throws InterruptedException
	 */
	private void populateDatabase() throws InterruptedException {

		final PageDownloader pageDownloader = new PageDownloader(
				UserManager.getUserProvider());

		// Here we keep current requests
		final Queue<PageRequest> requests = new LinkedBlockingQueue<PageRequest>();
		// Here we keep ids of the pages which we will download in the future
		final Queue<Integer> idsOfRemainingPages = idsOfAllPages(configuration.numberOfPages);

		final ExecutorService executor = Executors
				.newFixedThreadPool(configuration.threadPoolSize);

		for (int i = 1; i < configuration.pendingRequests; i++) {
			createAndStorePage(executor, idsOfRemainingPages, pageDownloader,
					requests);
		}

		while (requests.size() > 0) {
			final PageRequest request = requests.poll();
			final int id = request.getId();
			final int retiresLeft = request.getRetriesLeft() - 1;

			System.out.println("Getting data for " + id);

			try {
				request.getRequest().get(configuration.pageTimeout,
						TimeUnit.SECONDS);
				// We go here iff the above statement did not throw the
				// exception
				// in such a case we case assume that the page was downloaded
				// without any problems
				createAndStorePage(executor, idsOfRemainingPages,
						pageDownloader, requests);
			} catch (final InterruptedException e) {
				// This shouldn't happen
				e.printStackTrace();
				throw new RuntimeException(e);
			} catch (final ExecutionException e) {
				// This shouldn't happen
				e.printStackTrace();
				throw new RuntimeException(e);
			} catch (final TimeoutException e) {
				// Timeout generally inidicates problems with the webpage
				// We should try to download this page one more time
				request.getRequest().cancel(true);
				System.out.println("Retrying " + id);
				final Future<?> future = executor.submit(createAndStorePage(id,
						pageDownloader));
				requests.add(new PageRequest(id, future, retiresLeft));
			}

		}

	}

	private static void updateId(final WykopId id, final Session session) {

		final WykopId existing = (WykopId) session.get(WykopId.class,
				id.getWykopId());
		if (existing == null) {
			session.save(id);
		} else {
			if (existing.getPage() == null) {
				existing.setPage(id.getPage());
				existing.setErrorMessage(id.getErrorMessage());
			}
		}
	}

}


